package com.toobigtofail
{
	import Box2D.Common.Math.b2Vec2;
	import Box2D.Dynamics.b2Body;
	
	import com.toobigtofail.enitities.Background;
	import com.toobigtofail.enitities.Bat;
	import com.toobigtofail.enitities.CavernWall;
	import com.toobigtofail.enitities.Enemy;
	import com.toobigtofail.enitities.Player;
	import com.toobigtofail.enitities.Rock;
	import com.toobigtofail.shapes.*;
	import com.toobigtofail.utility.ContactListener;
	import com.toobigtofail.utility.Util;
	
	import fabulaexmachina.box2fp.Box2DEntity;
	import fabulaexmachina.box2fp.Box2DWorld;
	import fabulaexmachina.box2fp.graphics.SuperGraphiclist;
	
	import flash.display.BitmapData;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.getQualifiedClassName;
	
	import net.flashpunk.Entity;
	import net.flashpunk.FP;
	import net.flashpunk.Sfx;
	import net.flashpunk.Tween;
	import net.flashpunk.graphics.Graphiclist;
	import net.flashpunk.graphics.Image;
	import net.flashpunk.graphics.Text;
	import net.flashpunk.tweens.misc.NumTween;
	import net.flashpunk.utils.Draw;
	import net.flashpunk.utils.Ease;
	import net.flashpunk.utils.Input;
	import net.flashpunk.utils.Key;
	
	import punk.bloom.BloomLighting;
	import punk.bloom.BloomWrapper;
	import punk.blur.BlurCanvas;
	
	public class Game extends Box2DWorld
	{	
		//music embed
		[Embed(source="/assets/sfx/fallingLayer1.mp3")] private const MUSIC1:Class;
		[Embed(source="/assets/sfx/fallingLayer2.mp3")] private const MUSIC2:Class;
		[Embed(source="/assets/sfx/fallingLayer3.mp3")] private const MUSIC3:Class;
		[Embed(source="/assets/sfx/fallingLayer4.mp3")] private const MUSIC4:Class;
		[Embed(source="/assets/sfx/fallingLayer5.mp3")] private const MUSIC5:Class;
		[Embed(source="/assets/sfx/fallingLayer6.mp3")] private const MUSIC6:Class;
		[Embed(source="/assets/sfx/fallingLayer7.mp3")] private const MUSIC7:Class;
		[Embed(source="/assets/sfx/fallingLayer8.mp3")] private const MUSIC8:Class;
		[Embed(source="/assets/sfx/fallingLayer9.mp3")] private const MUSIC9:Class;
		
		
		
		[Embed(source = '/assets/fonts/AlanDen.ttf', embedAsCFF="false", fontFamily = 'Alan Den')] private static const ALAN_DEN_FONT:Class;
		
		public static const MIN_SPEED:Number = 8;
		public static const MAX_SPEED:Number = 30;
		
		public static const MIN_CAVERN_WIDTH:Number = 30;
		public static const MAX_CAVERN_WIDTH:Number = 300;
		
		private static const MAX_DIFFICULTY_SCORE:Number = 2000;
		
		private static const DIFFICULTY_MIN_WIDTH_OFFSET:Number = 400;
		private static const DIFFICULTY_MAX_WIDTH_OFFSET:Number = 200;
		private static const MIN_WIDTH:Number = 100;
		private static const MAX_WIDTH:Number = 500;
		
		private static const MOVE_WALL_TIME:Number = 1;
		
		private static const MAX_SPIKE_TIME:Number = 2;
		private static const SPIKE_TIME_DIFFICULTY_OFFSET:Number = 1.9;
		
		private static const MAX_BAT_TIME:Number = 8;
		private static const BAT_TIME_DIFFICULTY_OFFSET:Number = 3;
		
		private var _state:String = "instructions";
		
		private var _player:Player;
		private var _leftWall:CavernWall;
		private var _rightWall:CavernWall;
	
		private var _scoreEntity:Entity;
		private var _score:Number = 0;
		private var _highScore:Number = 0;
		private var _difficulty:Number = 0;

		//private var _playerPowerSpriteRect:Rectangle = new Rectangle(0,0,100,30);
		//private var _playerPowerSprite:BitmapData;
		//private var _playerPowerTween:NumTween;
		
		private var _background:Background;
				
		private var _movingLeft:Boolean = true;
		private var _moveWallTimer:Number = 1;
		private var _spikeTimer:Number = 0;
		private var _batTimer:Number = 0;
		
		private var _speed:Number = MIN_SPEED;
		
		//Music
		private var _music:Array = new Array(
			new Sfx(MUSIC1, playNextMusic),
			new Sfx(MUSIC2, playNextMusic),
			new Sfx(MUSIC3, playNextMusic),
			new Sfx(MUSIC4, playNextMusic),
			new Sfx(MUSIC5, playNextMusic),
			new Sfx(MUSIC6, playNextMusic),
			new Sfx(MUSIC7, playNextMusic),
			new Sfx(MUSIC8, playNextMusic),
			new Sfx(MUSIC9, playNextMusic)
		);
		private var _nextMusic:uint = 0;
		
		//sfx
		private var _sfx:Array = new Array;
		
		//bloom
		//private var _bloom:BloomLighting = new BloomLighting(100.0, 1);
		
		//blur
		private var _blur:BlurCanvas = new BlurCanvas(0.8);
		
		private var _instructionTimer:Number = 0;
		private static const INSTRUCTION_TIME_1:Number = 1;
		private static const INSTRUCTION_TIME_2:Number = 3;
		private static const INSTRUCTION_TIME_3:Number = 5;
		
		private var _instructionsHitSpace:Boolean = false;
		private var _instructionStep:uint = 0;
		
		public function Game()
		{
			super();
		}
		
		private function start():void {
			// Kill any leftover bats
			var bats:Array = new Array;
			getClass(Bat, bats);
			for each (var bat:Bat in bats) {
				remove(bat);
			}
			
			// Cavern Walls
			if (_state == "playing") {
				_leftWall = new CavernWall(FP.screen.width/2 - 250, FP.screen.width/2 - 175, CavernWall.LEFT);
				_rightWall = new CavernWall(FP.screen.width/2 + 120, FP.screen.width/2 + 205, CavernWall.RIGHT);
				_leftWall.color = 0xFF000000;
				_rightWall.color = 0xFF000000;
				add(_leftWall);
				add(_rightWall);
				
				_background = new Background();
				add(_background);
			}
			
			speed = MIN_SPEED;
			if (_player && _player.world) _player.remove();
			// Player
			_player = new Player(new Box(FP.screen.width/2, FP.screen.height/2 - Player.POS_OFFSET, 1, 1, b2Body.b2_dynamicBody));
			add(_player);
			_player.blur(_blur);
			
			if (_state == "instructions") {
				_player.allowBlur = false;
			}
			
			//score
			Text.size = 40;
			Text.font = "Alan Den";
			if (_scoreEntity && _scoreEntity.world) remove(_scoreEntity);
			_scoreEntity = new Entity(FP.screen.width/2, 30);
			add(_scoreEntity);
			
			if (_state == "playing") {
								
				playNextMusic();
			}
		}
		
		public function die():void {
			/*for each (var s:Sfx in _music) {
				s.stop();
			}
			_nextMusic=0;
			*/
			this.remove(_background);
			_player.remove();
			_leftWall.remove();
			_rightWall.remove();
			
			var enemies:Array = new Array;
			getType("enemy", enemies);
			getType("spike", enemies);
			getType("shit", enemies);
			
			for each (var e:Box2DEntity in enemies) {
				e.remove();
			}
			
			// Show end text and score/high score
			_highScore = Math.max(_highScore, _score);
			
			var restartText:Text = new Text("The End. Press R to Restart");
			restartText.x -= restartText.width/2;
			var scoreText:Text = new Text("You Traveled (( " + _score.toFixed(0) + " )) meters!");
			scoreText.x -= scoreText.width/2;
			scoreText.y = 200;
			var hScoreText:Text = new Text("High Score: (( " + _highScore.toFixed(0) + " ))");
			hScoreText.x = scoreText.x + 50;
			hScoreText.y = 260;
			
			var credText1:Text = new Text("Programming: Justin Peavey & Troy Fendall");
			credText1.size = 14;
			credText1.x -= 200;
			credText1.y = 400;
			var credText2:Text = new Text("Music: Reilly Peavey");
			credText2.size = 14;
			credText2.x -= 200;
			credText2.y = 420;
			var credText3:Text = new Text("Art: Reilly Peavey & Paul Maupoux");
			credText3.size = 14;
			credText3.x -= 200;
			credText3.y = 440;
			
			_scoreEntity.x = FP.screen.width/2;
			var gl:SuperGraphiclist = new SuperGraphiclist(restartText, scoreText, hScoreText, credText1, credText2, credText3);
			_scoreEntity.graphic = gl;
			
			score = 0;
			
			_state = "end";
		}
		
		override public function begin():void {
			setGravity(new b2Vec2 (0.0, 0.0));
			
			//blur
			_blur.layer = 1;
			_blur.color = 0xEEEEEE;
			add(_blur);
			
			//_bloom.layer = -1;
			//_bloom.color = 0xafffff;
			//add(_bloom);
			
			// player power
			//_playerPowerSprite = new BitmapData(_playerPowerSpriteRect.width, _playerPowerSpriteRect.height, true, 0);
			//_playerPowerTween = new NumTween(null, Tween.PERSIST);
			//addTween(_playerPowerTween);
			
			start();
			
			this.unpause();
			//doDebug();
			
			b2world.SetContactListener(new ContactListener);
		}
		
		override public function update():void {
			super.update();
			
			switch(_state) {
				case "instructions": updateInstructions(); break;
				case "playing": updatePlaying(); break;
				case "end": updateEnd(); break;
			}
		}
		
		private function updateInstructions():void {
			_instructionTimer += FP.elapsed;
			if (_instructionStep < 1) {
				var text0:Text = new Text("(use the arrows to move left and right)");
				text0.size = 14;
				text0.x -= text0.width/2;
				text0.y += 400;				
				if (_scoreEntity.graphic == null) _scoreEntity.graphic = new Graphiclist;
				(_scoreEntity.graphic as Graphiclist).add(text0);
				_instructionStep++;
			}
			if (_instructionTimer > INSTRUCTION_TIME_1 && _instructionStep < 2) {
				var text1:Text = new Text("It's dangerous to fall alone!");
				text1.x -= text1.width/2;
				(_scoreEntity.graphic as Graphiclist).add(text1);
				_instructionStep++;
				
				spawnRock(_player.x - 100, _player.y + 100).gravity = new b2Vec2(0,0);
				spawnRock(_player.x + 100, _player.y + 100).gravity = new b2Vec2(0,0);
			}
			if (_instructionTimer > INSTRUCTION_TIME_2 && _instructionStep < 3) {
				var text2:Text = new Text("Take this.");
				text2.x -= text2.width/2;
				text2.y += 50;
				(_scoreEntity.graphic as Graphiclist).add(text2);
				
				speed = MAX_SPEED;
				_player.allowBlur = true;
				
				_instructionStep++;
			}
			if (_instructionTimer > INSTRUCTION_TIME_3 && _instructionStep < 4) {
				var text3:Text = new Text("(press space to use \"this\")");
				text3.size = 14;
				text3.x -= text3.width/2;
				text3.y += 430;
				(_scoreEntity.graphic as Graphiclist).add(text3);
				
				_instructionStep++;
			}
			
			if (Input.pressed(Key.ENTER)) {
				var rocks:Array = new Array;
				getType("enemy", rocks);
				for each (var rock:Box2DEntity in rocks) {
					rock.remove();
				}
				_state = "playing";
				start();				
			}
			if (Input.pressed(Key.SPACE) && !_instructionsHitSpace) {
				//trace("space");
				var text4:Text = new Text("[ press enter to start ]");
				text4.x -= text4.width/2;
				text4.y += 300;
				if (_scoreEntity.graphic == null) _scoreEntity.graphic = new Graphiclist;
				(_scoreEntity.graphic as Graphiclist).add(text4);
				
				_instructionsHitSpace = true;
			}
		}
		
		private function updatePlaying():void {
			speed = speed*1.005;
			if (speed > MAX_SPEED) speed = MAX_SPEED;
			
			// Tween to make the power bar pulsate when it's full
			/*
			if (_player.isPowerReady() && !_playerPowerTween.active) {
				if (_playerPowerTween.value - 0.7 < 0.001) {
					_playerPowerTween.tween(0.5, 1, 0.2, Ease.quadInOut);	
				}
				else {
					_playerPowerTween.tween(1, 0.5, 0.2, Ease.quadInOut);	
				}
			}
			else if (!_player.isPowerReady()) {
				_playerPowerTween.active = false;
			}
			*/

			// Wall generation
			_moveWallTimer -= FP.elapsed;
			if (_moveWallTimer <= 0) {
				moveWalls();
				_moveWallTimer = MOVE_WALL_TIME / speed;
			}
			
			// Spawn some rock obstacles
			if (Util.random(0,100) < Math.min(5*difficulty)) {
				spawnRock();
			}
			
			// Spike creation
			if(_spikeTimer > (MAX_SPIKE_TIME - SPIKE_TIME_DIFFICULTY_OFFSET*difficulty))
			{
				var spikeWall:Number = Math.random();
				
				if(spikeWall < .6)
				{
					_leftWall.addEnemy = true;
				}
				if ( spikeWall > .4)
				{
					_rightWall.addEnemy = true
				}
				
				_spikeTimer = 0;
			}
			else
			{
				_spikeTimer += FP.elapsed; 
			}
			
			//Bat Spawn
			_batTimer += FP.elapsed;
			if(_batTimer > (MAX_BAT_TIME - BAT_TIME_DIFFICULTY_OFFSET*difficulty)) {
				_batTimer = 0;
				add(new Bat());
			}

			score = _score + FP.elapsed * speed;
			
			var text:Text = new Text(_score.toFixed(0));
			text.font = "Alan Den";
			_scoreEntity.graphic = text;			
			
		}
		
		private function moveWalls():void {
			// Calculate three components to the cavern and generate the walls accordingly
			// 1. center point of the cavern - up to 50 pixels off of the current center
			var cavCenter:Number = _leftWall.maxX + (_rightWall.minX - _leftWall.maxX)/2;
			cavCenter = Util.random(cavCenter - 50, cavCenter + 50);
			
			// 2. width of the cavern
			var cavWidth:Number = _rightWall.minX - _leftWall.maxX;
			var minWidth:Number = MIN_WIDTH + (1-difficulty)*DIFFICULTY_MIN_WIDTH_OFFSET;
			var maxWidth:Number = MAX_WIDTH - difficulty*DIFFICULTY_MAX_WIDTH_OFFSET;
			cavWidth = Util.random(Math.max(cavWidth-30, minWidth), Math.min(cavWidth+30, maxWidth));
			
			// 3. jaggedness of the walls
			var lJag:Number = _leftWall.maxX - _leftWall.minX;
			lJag = Util.random(0, 15);
			var rJag:Number = _rightWall.maxX - _rightWall.minX;
			rJag = Util.random(0, 15);
			
			// Make sure we stay in bounds
			//
			
			// Make sure our width fits in the screen
			var totalWidth:Number = cavWidth + lJag + rJag;
			if (totalWidth > (CavernWall.HIGHEST_X - CavernWall.LOWEST_X)) {
				cavWidth -= ((CavernWall.HIGHEST_X - CavernWall.LOWEST_X) - totalWidth);
			}
			
			// Make sure we're not too far left
			var farLeft:Number = Math.floor(cavCenter - cavWidth/2 - lJag);
			if (farLeft < CavernWall.LOWEST_X) {
				cavCenter += (CavernWall.LOWEST_X - farLeft);
			}
			
			// Make sure we're not too far right
			var farRight:Number = Math.ceil(cavCenter + cavWidth/2 + rJag);
			if (farRight > CavernWall.HIGHEST_X) {
				cavCenter -= (farRight - CavernWall.HIGHEST_X);
			}
			
			// recalculate bounds after adjustments
			farLeft  = cavCenter - cavWidth/2 - lJag;
			farRight = cavCenter + cavWidth/2 + rJag;
			
			// Set the wall parameters
			_leftWall.minX = farLeft;
			_leftWall.maxX = farLeft+lJag;
			_rightWall.minX = farRight-rJag;
			_rightWall.maxX = farRight;
		}
		
		private function spawnRock(x:Number = -1, y:Number = -1):Rock {
			var w:Number = Util.random(10, 60);
			
			if (y == -1) y = FP.screen.height + w;			
			if (x == -1) x = Util.random(_leftWall.maxX + w/2, _rightWall.minX - w/2);
			
			var e:Rock = new Rock(new Octagon(x, y, w/(2*scale), b2Body.b2_dynamicBody));
			e.gravity = new b2Vec2(0, -10);
			add(e);
			
			return e;
		}
		
		private function updateEnd():void {
			if (Input.pressed(Key.R)) {
				_state = "playing";
				for each (var s:Sfx in _music) {
				s.stop();
				}
				_nextMusic=0;
				
				start();			
			}
		}
		
		override public function render():void {
			super.render();
			if (_state == "playing") {
				// Draw the bar that represents how much power you have
				/*
				Draw.setTarget(_playerPowerSprite);
				_playerPowerSprite.fillRect(_playerPowerSpriteRect, 0);
				Draw.rect(0, 0, (_playerPowerSprite.width * (_player.power / Player.MAX_POWER)), _playerPowerSprite.height, 0xFFFC51, _playerPowerTween.active ? _playerPowerTween.value : 1);
				FP.buffer.copyPixels(_playerPowerSprite, _playerPowerSpriteRect, new Point(FP.screen.width/2 + 100, 30));
				*/
			}
		}
		
		private function playNextMusic():void {
			if(_state == "end")
			{
				(_music[_nextMusic-1] as Sfx).loop();
			}
			else if (_nextMusic == _music.length-1) {
				(_music[_nextMusic] as Sfx).loop();	
			}
			else {
				(_music[_nextMusic] as Sfx).play();
				_nextMusic++;
			}
		}
		
		private function set score(s:Number):void { 
			_score = s;
			_difficulty = Math.min(_score, MAX_DIFFICULTY_SCORE)/MAX_DIFFICULTY_SCORE;
		}
		
		private function get difficulty():Number { return _difficulty; }
		
		public function get speed():Number { return _speed; }
		
		public function set speed(s:Number):void {
			if (s < MIN_SPEED) {
				s = MIN_SPEED;
			}
			
			_blur.fallSpeed = s/3.5;
			_speed = s;
			if (_leftWall) _leftWall.speed = _speed;
			if (_rightWall) _rightWall.speed = _speed;
			if (_background) _background.speed = _speed;
			var enemyArray:Array = new Array();
			FP.world.getClass(Enemy,enemyArray);
			for each (var i:Enemy in enemyArray)
			{
				i.speed = _speed;
			}
			
			var rockArray:Array = new Array;
			getClass(Rock, rockArray);
			for each (var r:Rock in rockArray) {
				r.gravity = new b2Vec2(0, -(_speed - MIN_SPEED));
			}
		}
	}
}